package com.spring.mvc.mina.test.demo;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class NioServer implements Runnable {
	
	private InetAddress hostAddress;
	private int port;
	
	private ServerSocketChannel serverChannel;
	
	private Selector selector;
	
	private ByteBuffer readBuffer=ByteBuffer.allocate(8192);
	
	private EchoWorker worker;
	
	private List<ChangeRequest> pendingChanges=new LinkedList<ChangeRequest>();
	
	private Map<SocketChannel,List<ByteBuffer>> pendingData=new HashMap<SocketChannel,List<ByteBuffer>>();
	
	public NioServer(InetAddress hostAddress,int port ,EchoWorker worker) throws IOException {
		this.hostAddress=hostAddress;
		this.port=port;
		this.selector=this.initSelector();
		this.worker=worker;
	}

	private Selector initSelector() throws IOException {
		//Selector.open();
		Selector socketSelector=SelectorProvider.provider().openSelector();
		
		//获取ServerSocketChannel 对象
		this.serverChannel=ServerSocketChannel.open();
		//设置为非阻塞模式
		this.serverChannel.configureBlocking(false);
		
		InetSocketAddress isa=new InetSocketAddress(this.hostAddress, port);
		//绑定Ip及端口
		serverChannel.socket().bind(isa);
		//注册OP_ACCEPT事件
		serverChannel.register(socketSelector, SelectionKey.OP_ACCEPT);
		return socketSelector;
	}
	
	public void send(SocketChannel socket,byte[] data){
		synchronized (this.pendingChanges) {
			this.pendingChanges.add(new ChangeRequest(socket,ChangeRequest.CHANGEOPS,SelectionKey.OP_WRITE));
			synchronized (this.pendingData) {
				List<ByteBuffer> queue=this.pendingData.get(socket);
				if(queue==null){
					queue=new ArrayList<ByteBuffer>();
					this.pendingData.put(socket, queue);
				}
				queue.add(ByteBuffer.wrap(data));
			}
		}
		this.selector.wakeup();
	}

	public void run() {
		while(true){
			try{
				synchronized (this.pendingChanges) {
					Iterator<ChangeRequest> changes=this.pendingChanges.iterator();
					while(changes.hasNext()){
						ChangeRequest change=changes.next();
						switch(change.type){
						case ChangeRequest.CHANGEOPS:
							SelectionKey key=change.socket.keyFor(this.selector);
							key.interestOps(change.ops);
							break;
						}
					}
					System.out.println("pendingChanges Clear()");
					this.pendingChanges.clear();
				}
				System.out.println(selector);
				this.selector.select();
				System.out.println(2);
				
				Iterator<SelectionKey> selectedKeys=this.selector.selectedKeys().iterator();
				while(selectedKeys.hasNext()){
					SelectionKey key=selectedKeys.next();
					selectedKeys.remove();
					
					if(!key.isValid()){
						continue;
					}
					
					if(key.isAcceptable()){
						this.accept(key);
					} else if(key.isReadable()){
						this.read(key);
					} else if(key.isWritable()){
						this.write(key);
					}
				}
			} catch(Exception e){
				e.printStackTrace();
			}
		}
	}

	private void accept(SelectionKey key) throws IOException{
		ServerSocketChannel serverSocketChannel=(ServerSocketChannel) key.channel();
		
		SocketChannel socketChannel=serverSocketChannel.accept();
		//Socket socket=socketChannel.socket();
		socketChannel.configureBlocking(false);
		
		socketChannel.register(this.selector, SelectionKey.OP_READ);
	}
	
	private void read(SelectionKey key) throws IOException{
		SocketChannel socketChannel=(SocketChannel) key.channel();
		
		this.readBuffer.clear();
		
		int numRead=0;
		try{
			numRead=socketChannel.read(this.readBuffer);
		}catch(IOException e){
			key.cancel();
			socketChannel.close();
			return;
		}
		if(numRead==-1){
			key.channel().close();
			key.cancel();
			return;
		}
		
		this.worker.processData(this,socketChannel,this.readBuffer.array(),numRead);
	}
	
	private void write(SelectionKey key) throws IOException {
		SocketChannel socketChannel=(SocketChannel) key.channel();
		synchronized (this.pendingData) {
			List<ByteBuffer> queue=this.pendingData.get(socketChannel);
			while(queue!=null && !queue.isEmpty()){
				ByteBuffer buf=queue.get(0);
				socketChannel.write(buf);
				if(buf.remaining()>0){
					break;
				}
				queue.remove(0);
			}
			if(queue!=null && queue.isEmpty()){
				key.interestOps(SelectionKey.OP_READ);
			}
		}
	}

	public static void main(String[] args) throws IOException {
		EchoWorker worker=new EchoWorker();
		new Thread(worker).start();
		new Thread(new NioServer(null, 9090, worker)).start();
	}
}
